{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit GPSParser;

interface

uses Classes, Mediate_TLB, Dialogs, GPSConsts, Math;

const
  GPS_DELIM = ','; // GPS sentence delimiter
  GPS_LINE_END = #13; //Each line terminates with carraige return

type
  TAHMGPSParser = class
  private
    FBuffer: String;
    FTokenizer: TStringList;

    FLastCommand: String;

    //RMC Sentence
    FSatelitteTime: Single;
    FFixStatus: String;
    FLattitude: Double;
    FLattitudeHemisphere: String;
    FLongitude: Double;
    FLongitudeHemisphere: String;
    FSpeed: Single;
    FBearing: Single;
    FUTCDate: Integer;
    FChecksum: String;

    //GGA Sentence
    FGGATime: String;
    FGGALatitude: Single;
    FGGALongitude: Single;
    FGGAFix: String;
    FGGANumberofSats: Integer;
    FGGAHDOP: Single;
    FGGAAltitude: Single;
    FGGAlastDGPUpdateTime: String;
    FGGADGPSrefStnId: String;
    FGGAChecksum: String;

    FMediate: IMediateServer;
  protected
    procedure ParseRMCSentence;
    procedure ParseGGASentence;
    procedure ParseSentence(Sentence: String);
    function LatDegToDec(latStr:String):Double;
    function LongDegToDec(longStr:String):Double;
  public
    constructor Create;
    destructor Destroy; override;
    property Buffer: String read FBuffer; // debugging only
    property LastCommand: String read FLastCommand;

    //GPGGA fileds
    property GGATime: String read FGGATime;
    property GGALatitude: Single read FGGALatitude;
    property GGALongitude: Single read FGGALongitude;
    property GGAFix: String read FGGAFix;
    property GGANumberofSats: Integer read FGGANumberofSats;
    property GGAHDOP: Single read FGGAHDOP;
    property GGAAltitude: Single read FGGAAltitude;
    property GGAlastDGPUpdateTime: String read FGGAlastDGPUpdateTime;
    property GGADGPSrefStnId: String read FGGADGPSrefStnId;
    property GGAChecksum: String read FGGAChecksum;

    //GPRMC fields
    property SatelitteTime: Single read FSatelitteTime;
    property FixStatus: String read FFixStatus;
    property Lattitude: Double read FLattitude;
    property LattitudeHemisphere: String read FLattitudeHemisphere;
    property Longitude: Double read FLongitude;
    property LongitudeHemisphere: String read FLongitudeHemisphere;
    property Speed: Single read FSpeed;
    property Bearing: Single read FBearing;
    property UTCDate: Integer read FUTCDate;
    property Checksum: String read FChecksum;

    procedure Parse(Data: String; FMediateServer: IMediateServer);
  end;

implementation

uses SysUtils;

// TAHMGPSParser

constructor TAHMGPSParser.Create;
begin
  inherited;

  // Create long term string tokenizer
  FTokenizer := TStringList.Create;
end;

destructor TAHMGPSParser.Destroy;
begin
  // Free long term string tokenizer
  FTokenizer.Free;

  inherited;
end;

procedure TAHMGPSParser.ParseGGASentence;
begin
   try
     FLastCommand := FTokenizer[0];
     FGGATime := FTokenizer[1];
     FGGALatitude :=  StrToFloatDef(FTokenizer[2], 0);
     FGGALongitude := StrToFloatDef(FTokenizer[3], 0);
     FGGAFix := FTokenizer[4];
     FGGANumberOfSats := StrToIntDef(FTokenizer[5],0);
     FGGAHDOP := StrToFloatDef(FTokenizer[6],0);
     FGGAAltitude := StrToFloatDef(FTokenizer[7],0);
     FGGAlastDGPUpdateTime := FTokenizer[8];
     FGGADGPSrefStnId := FTokenizer[9];
     FGGAChecksum := FTokenizer[10];
   except
     // Invalid sentence
   end;
end;

procedure TAHMGPSParser.ParseRMCSentence;
begin
  try
    //GPRMC is the main sentence which gives us all we need to know about global position
    //Sample sentence $GPRMC,040302.663,A,3939.7,N,10506.6,W,0.27,358.86,200804,,*1A
    FMediate.LogMessage(llDebug, 'ORIG Lat='+FTokenizer[3]+' ORIG Long='+FTokenizer[5]);
    FLastCommand := FTokenizer[0];
    FSatelitteTime := StrToFloatDef(FTokenizer[1], 0);
    FFixStatus := FTokenizer[2];
    FLattitudeHemisphere := FTokenizer[4];
    FLongitudeHemisphere := FTokenizer[6];

    FLattitude := LatDegToDec(FTokenizer[3]);
    FLongitude := LongDegToDec(FTokenizer[5]);

    FSpeed:= StrToFloatDef(FTokenizer[7],0);
    FBearing:= StrToFloatDef(FTokenizer[8],0);
    FUTCDate:= StrToIntDef(FTokenizer[9],0);
    //Token 10 is unused
    FChecksum:= FTokenizer[11];
    FMediate.LogMessage(llDebug, Format('     Lat=%12.4f      Long=%12.4f', [FLattitude, FLongitude]));
  except
    // Invalid sentence structure
  end;
end;

procedure TAHMGPSParser.ParseSentence(Sentence: String);
begin
  // Tokenize the sentence using comma delimiter
  FTokenizer.CommaText := Sentence;
  if FTokenizer.Count > 0 then
  begin
    // Identify the sentence type
    if Pos('$GPRMC', FTokenizer[0]) > 0 then ParseRMCSentence;
    if Pos('$GPGGA', FTokenizer[0]) > 0 then ParseGGASentence;
  end;
end;

//TODO - Passed in MediateServer so that I can using the logging to debug
//May want to take this out afterwards
procedure TAHMGPSParser.Parse(Data: String; FMediateServer: IMediateServer);
var
  iPos: Integer;
  Sentence: String;
begin
  // Append incoming gps data to buffer
  FBuffer := FBuffer + Data;
  FMediate := FMediateServer;
  //FMediate.LogMessage(llDebug, 'Data='+FBuffer);
  // Check for sentence delimiter
  iPos :=  Pos(GPS_LINE_END, FBuffer);
  if (iPos < 1) then
  begin
    FMediate.ClearMetaData(META_GPS);
    exit;
  end;
  while iPos > 0 do
  begin
    Sentence := Copy(FBuffer, 1, iPos - 1);
    FBuffer := Copy(FBuffer, iPos + 1, Length(FBuffer));
    //FMediate.LogMessage(llDebug, 'Sentence:'+Sentence);
    // Attempt to parse this sentence
    ParseSentence(Sentence);

    // Check for another sentence delimiter
    iPos :=  Pos(GPS_LINE_END, FBuffer);
  end;
end;

function TAHMGPSParser.LatDegToDec(latStr:String):Double;
var
  degreeslat, minslat, degrees, answer: Double;
begin
  degrees := StrToFloatDef(latStr, 0);
  degreeslat := StrToFloatDef(Copy(latStr,1,2),0);
  minslat := StrToFloatDef(Copy(latStr,3,MaxInt),0);
  FMediate.LogMessage(llDebug, Format('LATDEG=%12.4f',[degrees]));

  answer := degreeslat+(minslat/60);
  if (FLattitudeHemisphere = 'S') then
    Result := answer*-1
  else
    Result := answer;
end;

function TAHMGPSParser.LongDegToDec(longStr:String):Double;
var
  degreeslong, minslong, degrees, answer: Double;
begin
  degrees := StrToFloatDef(longStr, 0);
  FMediate.LogMessage(llDebug, Format('LONGDEG=%12.4f',[degrees]));
  degreeslong := StrToFloatDef(Copy(longStr,1,3),0);
  minslong := StrToFloatDef(Copy(longStr,4,MaxInt),0);

  answer := degreeslong+(minslong/60);
  if (FLongitudeHemisphere ='W') then
    Result := answer*-1
  else
    Result := answer;
end;

end.
